/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package wx.xml.xalan.xalan.transformer;

import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import wx.xml.xalan.xalan.res.XSLMessages;
import wx.xml.xalan.xalan.res.XSLTErrorResources;
import wx.xml.xalan.xalan.templates.OutputProperties;
import wx.xml.xalan.xml.serializer.Method;
import wx.xml.xalan.xml.serializer.Serializer;
import wx.xml.xalan.xml.serializer.SerializerFactory;
import wx.xml.xalan.xml.utils.DOMBuilder;
import wx.xml.xalan.xml.utils.XMLReaderManager;

/**
 * This class implements an identity transformer for
 * {@link javax.xml.transform.sax.SAXTransformerFactory#newTransformerHandler()}
 * and {@link javax.xml.transform.TransformerFactory#newTransformer()}.  It
 * simply feeds SAX events directly to a serializer ContentHandler, if the
 * result is a stream.  If the result is a DOM, it will send the events to
 * {@link wx.xml.xalan.xml.utils.DOMBuilder}.  If the result is another
 * content handler, it will simply pass the events on.
 */
public class TransformerIdentityImpl extends Transformer
    implements TransformerHandler, DeclHandler {

    boolean m_flushedStartDoc = false;
    /**
     * The URIResolver, which is unused, but must be returned to fullfill the
     * TransformerHandler interface.
     */
    URIResolver m_URIResolver;
    /**
     * Flag to set if we've found the first element, so we can tell if we have
     * to check to see if we should create an HTML serializer.
     */
    boolean m_foundFirstElement;
    /**
     * This is null unless we own the stream.
     */
    private java.io.FileOutputStream m_outputStream = null;
    /**
     * The content handler where result events will be sent.
     */
    private ContentHandler m_resultContentHandler;
    /**
     * The lexical handler where result events will be sent.
     */
    private LexicalHandler m_resultLexicalHandler;
    /**
     * The DTD handler where result events will be sent.
     */
    private DTDHandler m_resultDTDHandler;
    /**
     * The Decl handler where result events will be sent.
     */
    private DeclHandler m_resultDeclHandler;
    /**
     * The Serializer, which may or may not be null.
     */
    private Serializer m_serializer;
    /**
     * The Result object.
     */
    private Result m_result;
    /**
     * The system ID, which is unused, but must be returned to fullfill the
     * TransformerHandler interface.
     */
    private String m_systemID;
    /**
     * The parameters, which is unused, but must be returned to fullfill the
     * Transformer interface.
     */
    private Hashtable m_params;
    /**
     * The error listener for TrAX errors and warnings.
     */
    private ErrorListener m_errorListener =
        new wx.xml.xalan.xml.utils.DefaultErrorHandler(false);
    /**
     * The output properties.
     */
    private OutputProperties m_outputFormat;
    /**
     * State of the secure processing feature.
     */
    private boolean m_isSecureProcessing = false;

    /**
     * Constructor TransformerIdentityImpl creates an identity transform.
     */
    public TransformerIdentityImpl(boolean isSecureProcessing) {
        m_outputFormat = new OutputProperties(Method.XML);
        m_isSecureProcessing = isSecureProcessing;
    }

    /**
     * Constructor TransformerIdentityImpl creates an identity transform.
     */
    public TransformerIdentityImpl() {
        this(false);
    }

    /**
     * Enables the user of the TransformerHandler to set the
     * to set the Result for the transformation.
     *
     * @param result A Result instance, should not be null.
     * @throws IllegalArgumentException if result is invalid for some reason.
     */
    public void setResult(Result result) throws IllegalArgumentException {
        if (null == result)
            throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_RESULT_NULL, null)); //"Result should not be null");
        m_result = result;
    }

    /**
     * Get the base ID (URI or system ID) from where relative
     * URLs will be resolved.
     *
     * @return The systemID that was set with {@link #setSystemId}.
     */
    public String getSystemId() {
        return m_systemID;
    }

    /**
     * Set the base ID (URI or system ID) from where relative
     * URLs will be resolved.
     *
     * @param systemID Base URI for the source tree.
     */
    public void setSystemId(String systemID) {
        m_systemID = systemID;
    }

    ////////////////////////////////////////////////////////////////////
    // Default implementation of DTDHandler interface.
    ////////////////////////////////////////////////////////////////////

    /**
     * Get the Transformer associated with this handler, which
     * is needed in order to set parameters and output properties.
     *
     * @return non-null reference to the transformer.
     */
    public Transformer getTransformer() {
        return this;
    }

    /**
     * Reset the status of the transformer.
     */
    public void reset() {
        m_flushedStartDoc = false;
        m_foundFirstElement = false;
        m_outputStream = null;
        clearParameters();
        m_result = null;
        m_resultContentHandler = null;
        m_resultDeclHandler = null;
        m_resultDTDHandler = null;
        m_resultLexicalHandler = null;
        m_serializer = null;
        m_systemID = null;
        m_URIResolver = null;
        m_outputFormat = new OutputProperties(Method.XML);
    }

    ////////////////////////////////////////////////////////////////////
    // Default implementation of ContentHandler interface.
    ////////////////////////////////////////////////////////////////////

    /**
     * Create a result ContentHandler from a Result object, based
     * on the current OutputProperties.
     *
     * @param outputTarget Where the transform result should go,
     *                     should not be null.
     * @return A valid ContentHandler that will create the
     * result tree when it is fed SAX events.
     * @throws TransformerException
     */
    private void createResultContentHandler(Result outputTarget)
        throws TransformerException {

        if (outputTarget instanceof SAXResult) {
            SAXResult saxResult = (SAXResult) outputTarget;

            m_resultContentHandler = saxResult.getHandler();
            m_resultLexicalHandler = saxResult.getLexicalHandler();

            if (m_resultContentHandler instanceof Serializer) {

                // Dubious but needed, I think.
                m_serializer = (Serializer) m_resultContentHandler;
            }
        } else if (outputTarget instanceof DOMResult) {
            DOMResult domResult   = (DOMResult) outputTarget;
            Node      outputNode  = domResult.getNode();
            Node      nextSibling = domResult.getNextSibling();
            Document  doc;
            short     type;

            if (null != outputNode) {
                type = outputNode.getNodeType();
                doc = (Node.DOCUMENT_NODE == type)
                      ? (Document) outputNode : outputNode.getOwnerDocument();
            } else {
                try {
                    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

                    dbf.setNamespaceAware(true);

                    if (m_isSecureProcessing) {
                        try {
                            dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
                        } catch (ParserConfigurationException pce) {
                        }
                    }

                    DocumentBuilder db = dbf.newDocumentBuilder();

                    doc = db.newDocument();
                } catch (ParserConfigurationException pce) {
                    throw new TransformerException(pce);
                }

                outputNode = doc;
                type = outputNode.getNodeType();

                ((DOMResult) outputTarget).setNode(outputNode);
            }

            DOMBuilder domBuilder =
                (Node.DOCUMENT_FRAGMENT_NODE == type)
                ? new DOMBuilder(doc, (DocumentFragment) outputNode)
                : new DOMBuilder(doc, outputNode);

            if (nextSibling != null)
                domBuilder.setNextSibling(nextSibling);

            m_resultContentHandler = domBuilder;
            m_resultLexicalHandler = domBuilder;
        } else if (outputTarget instanceof StreamResult) {
            StreamResult sresult = (StreamResult) outputTarget;

            try {
                Serializer serializer =
                    SerializerFactory.getSerializer(m_outputFormat.getProperties());

                m_serializer = serializer;

                if (null != sresult.getWriter())
                    serializer.setWriter(sresult.getWriter());
                else if (null != sresult.getOutputStream())
                    serializer.setOutputStream(sresult.getOutputStream());
                else if (null != sresult.getSystemId()) {
                    String fileURL = sresult.getSystemId();

                    if (fileURL.startsWith("file:///")) {
                        if (fileURL.substring(8).indexOf(":") > 0) {
                            fileURL = fileURL.substring(8);
                        } else {
                            fileURL = fileURL.substring(7);
                        }
                    } else if (fileURL.startsWith("file:/")) {
                        if (fileURL.substring(6).indexOf(":") > 0) {
                            fileURL = fileURL.substring(6);
                        } else {
                            fileURL = fileURL.substring(5);
                        }
                    }

                    m_outputStream = new java.io.FileOutputStream(fileURL);
                    serializer.setOutputStream(m_outputStream);
                } else
                    throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_NO_OUTPUT_SPECIFIED, null)); //"No output specified!");

                m_resultContentHandler = serializer.asContentHandler();
            } catch (IOException ioe) {
                throw new TransformerException(ioe);
            }
        } else {
            throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_TO_RESULT_TYPE, new Object[]{outputTarget.getClass().getName()})); //"Can't transform to a Result of type "
            // + outputTarget.getClass().getName()
            // + "!");
        }

        if (m_resultContentHandler instanceof DTDHandler)
            m_resultDTDHandler = (DTDHandler) m_resultContentHandler;

        if (m_resultContentHandler instanceof DeclHandler)
            m_resultDeclHandler = (DeclHandler) m_resultContentHandler;

        if (m_resultContentHandler instanceof LexicalHandler)
            m_resultLexicalHandler = (LexicalHandler) m_resultContentHandler;
    }

    /**
     * Process the source tree to the output result.
     *
     * @param source       The input for the source tree.
     * @param outputTarget The output target.
     * @throws TransformerException If an unrecoverable error occurs
     *                              during the course of the transformation.
     */
    public void transform(Source source, Result outputTarget)
        throws TransformerException {

        createResultContentHandler(outputTarget);

        /*
         * According to JAXP1.2, new SAXSource()/StreamSource()
         * should create an empty input tree, with a default root node.
         * new DOMSource()creates an empty document using DocumentBuilder.
         * newDocument(); Use DocumentBuilder.newDocument() for all 3 situations,
         * since there is no clear spec. how to create an empty tree when
         * both SAXSource() and StreamSource() are used.
         */
        if ((source instanceof StreamSource && source.getSystemId() == null &&
            ((StreamSource) source).getInputStream() == null &&
            ((StreamSource) source).getReader() == null) ||
            (source instanceof SAXSource &&
                ((SAXSource) source).getInputSource() == null &&
                ((SAXSource) source).getXMLReader() == null) ||
            (source instanceof DOMSource && ((DOMSource) source).getNode() == null)) {
            try {
                DocumentBuilderFactory builderF = DocumentBuilderFactory.newInstance();
                DocumentBuilder        builder  = builderF.newDocumentBuilder();
                String                 systemID = source.getSystemId();
                source = new DOMSource(builder.newDocument());

                // Copy system ID from original, empty Source to new Source
                if (systemID != null) {
                    source.setSystemId(systemID);
                }
            } catch (ParserConfigurationException e) {
                throw new TransformerException(e.getMessage());
            }
        }

        try {
            if (source instanceof DOMSource) {
                DOMSource dsource = (DOMSource) source;

                m_systemID = dsource.getSystemId();

                Node dNode = dsource.getNode();

                if (null != dNode) {
                    try {
                        if (dNode.getNodeType() == Node.ATTRIBUTE_NODE)
                            this.startDocument();
                        try {
                            if (dNode.getNodeType() == Node.ATTRIBUTE_NODE) {
                                String data  = dNode.getNodeValue();
                                char[] chars = data.toCharArray();
                                characters(chars, 0, chars.length);
                            } else {
                                wx.xml.xalan.xml.serializer.TreeWalker walker;
                                walker = new wx.xml.xalan.xml.serializer.TreeWalker(this, m_systemID);
                                walker.traverse(dNode);
                            }
                        } finally {
                            if (dNode.getNodeType() == Node.ATTRIBUTE_NODE)
                                this.endDocument();
                        }
                    } catch (SAXException se) {
                        throw new TransformerException(se);
                    }

                    return;
                } else {
                    String messageStr = XSLMessages.createMessage(
                        XSLTErrorResources.ER_ILLEGAL_DOMSOURCE_INPUT, null);

                    throw new IllegalArgumentException(messageStr);
                }
            }

            InputSource xmlSource = SAXSource.sourceToInputSource(source);

            if (null == xmlSource) {
                throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_CANNOT_TRANSFORM_SOURCE_TYPE, new Object[]{source.getClass().getName()})); //"Can't transform a Source of type "
                //+ source.getClass().getName() + "!");
            }

            if (null != xmlSource.getSystemId())
                m_systemID = xmlSource.getSystemId();

            XMLReader reader        = null;
            boolean   managedReader = false;

            //cleanup
            ContentHandler      oldContentHandler = null;
            DTDHandler          oldDtdHandler     = null;
            boolean             isDtdHandlerSet   = false;
            Map<String, Object> oldProperties     = new HashMap<String, Object>();

            try {
                if (source instanceof SAXSource) {
                    reader = ((SAXSource) source).getXMLReader();
                }

                if (null == reader) {
                    try {
                        reader = XMLReaderManager.getInstance().getXMLReader();
                        managedReader = true;
                    } catch (SAXException se) {
                        throw new TransformerException(se);
                    }
                } else {
                    try {
                        reader.setFeature("http://xml.org/sax/features/namespace-prefixes",
                            true);
                    } catch (org.xml.sax.SAXException se) {
                        // We don't care.
                    }
                }

                // Get the input content handler, which will handle the
                // parse events and create the source tree.
                ContentHandler inputHandler = this;
                oldContentHandler = reader.getContentHandler();
                reader.setContentHandler(inputHandler);

                if (inputHandler instanceof org.xml.sax.DTDHandler) {
                    isDtdHandlerSet = true;
                    oldDtdHandler = reader.getDTDHandler();
                    reader.setDTDHandler((org.xml.sax.DTDHandler) inputHandler);
                }

                try {
                    if (inputHandler instanceof org.xml.sax.ext.LexicalHandler) {
                        oldProperties.put(
                            "http://xml.org/sax/properties/lexical-handler",
                            reader.getProperty("http://xml.org/sax/properties/lexical-handler"));

                        reader.setProperty("http://xml.org/sax/properties/lexical-handler",
                            inputHandler);
                    }

                    if (inputHandler instanceof org.xml.sax.ext.DeclHandler) {
                        oldProperties.put(
                            "http://xml.org/sax/properties/declaration-handler",
                            reader.getProperty("http://xml.org/sax/properties/declaration-handler"));

                        reader.setProperty(
                            "http://xml.org/sax/properties/declaration-handler",
                            inputHandler);
                    }
                } catch (org.xml.sax.SAXException se) {
                }

                try {
                    if (inputHandler instanceof org.xml.sax.ext.LexicalHandler) {
                        oldProperties.put(
                            "http://xml.org/sax/handlers/LexicalHandler",
                            reader.getProperty("http://xml.org/sax/handlers/LexicalHandler"));

                        reader.setProperty("http://xml.org/sax/handlers/LexicalHandler",
                            inputHandler);
                    }

                    if (inputHandler instanceof org.xml.sax.ext.DeclHandler) {
                        oldProperties.put(
                            "http://xml.org/sax/handlers/DeclHandler",
                            reader.getProperty("http://xml.org/sax/handlers/DeclHandler"));

                        reader.setProperty("http://xml.org/sax/handlers/DeclHandler",
                            inputHandler);
                    }
                } catch (org.xml.sax.SAXNotRecognizedException snre) {
                }

                reader.parse(xmlSource);
            } catch (wx.xml.xalan.xml.utils.WrappedRuntimeException wre) {
                Throwable throwable = wre.getException();

                while (throwable
                    instanceof wx.xml.xalan.xml.utils.WrappedRuntimeException) {
                    throwable =
                        ((wx.xml.xalan.xml.utils.WrappedRuntimeException) throwable).getException();
                }

                throw new TransformerException(wre.getException());
            } catch (org.xml.sax.SAXException se) {
                throw new TransformerException(se);
            } catch (IOException ioe) {
                throw new TransformerException(ioe);
            } finally {
                reader.setContentHandler(oldContentHandler);
                if (isDtdHandlerSet) {
                    reader.setDTDHandler(oldDtdHandler);
                }

                for (Entry<String, Object> oldProperty : oldProperties.entrySet()) {
                    try {
                        reader.setProperty(oldProperty.getKey(), oldProperty.getValue());
                    } catch (SAXNotRecognizedException e) {
                    } catch (SAXNotSupportedException e) {
                    }
                }

                if (managedReader) {
                    XMLReaderManager.getInstance().releaseXMLReader(reader);
                }
            }
        } finally {
            if (null != m_outputStream) {
                try {
                    m_outputStream.close();
                } catch (IOException ioe) {
                }
                m_outputStream = null;
            }
        }
    }

    /**
     * Add a parameter for the transformation.
     *
     * <p>Pass a qualified name as a two-part string, the namespace URI
     * enclosed in curly braces ({}), followed by the local name. If the
     * name has a null URL, the String only contain the local name. An
     * application can safely check for a non-null URI by testing to see if the first
     * character of the name is a '{' character.</p>
     * <p>For example, if a URI and local name were obtained from an element
     * defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
     * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
     * no prefix is used.</p>
     *
     * @param name  The name of the parameter, which may begin with a namespace URI
     *              in curly braces ({}).
     * @param value The value object.  This can be any valid Java object. It is
     *              up to the processor to provide the proper object coersion or to simply
     *              pass the object on for use in an extension.
     */
    public void setParameter(String name, Object value) {
        if (value == null) {
            throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_SET_PARAM_VALUE, new Object[]{name}));
        }

        if (null == m_params) {
            m_params = new Hashtable();
        }

        m_params.put(name, value);
    }

    /**
     * Get a parameter that was explicitly set with setParameter
     * or setParameters.
     *
     * <p>This method does not return a default parameter value, which
     * cannot be determined until the node context is evaluated during
     * the transformation process.
     *
     * @param name Name of the parameter.
     * @return A parameter that has been set with setParameter.
     */
    public Object getParameter(String name) {

        if (null == m_params)
            return null;

        return m_params.get(name);
    }

    /**
     * Clear all parameters set with setParameter.
     */
    public void clearParameters() {

        if (null == m_params)
            return;

        m_params.clear();
    }

    /**
     * Get an object that will be used to resolve URIs used in
     * document(), etc.
     *
     * @return An object that implements the URIResolver interface,
     * or null.
     */
    public URIResolver getURIResolver() {
        return m_URIResolver;
    }

    /**
     * Set an object that will be used to resolve URIs used in
     * document().
     *
     * <p>If the resolver argument is null, the URIResolver value will
     * be cleared, and the default behavior will be used.</p>
     *
     * @param resolver An object that implements the URIResolver interface,
     *                 or null.
     */
    public void setURIResolver(URIResolver resolver) {
        m_URIResolver = resolver;
    }

    /**
     * Get a copy of the output properties for the transformation.
     *
     * <p>The properties returned should contain properties set by the user,
     * and properties set by the stylesheet, and these properties
     * are "defaulted" by default properties specified by <a href="http://www.w3.org/TR/xslt#output">section 16 of the
     * XSL Transformations (XSLT) W3C Recommendation</a>.  The properties that
     * were specifically set by the user or the stylesheet should be in the base
     * Properties list, while the XSLT default properties that were not
     * specifically set should be the default Properties list.  Thus,
     * getOutputProperties().getProperty(String key) will obtain any
     * property in that was set by {@link #setOutputProperty},
     * {@link #setOutputProperties}, in the stylesheet, <em>or</em> the default
     * properties, while
     * getOutputProperties().get(String key) will only retrieve properties
     * that were explicitly set by {@link #setOutputProperty},
     * {@link #setOutputProperties}, or in the stylesheet.</p>
     *
     * <p>Note that mutation of the Properties object returned will not
     * effect the properties that the transformation contains.</p>
     *
     * <p>If any of the argument keys are not recognized and are not
     * namespace qualified, the property will be ignored.  In other words the
     * behaviour is not orthogonal with setOutputProperties.</p>
     *
     * @return A copy of the set of output properties in effect
     * for the next transformation.
     * @see javax.xml.transform.OutputKeys
     * @see java.util.Properties
     */
    public Properties getOutputProperties() {
        return (Properties) m_outputFormat.getProperties().clone();
    }

    /**
     * Set the output properties for the transformation.  These
     * properties will override properties set in the Templates
     * with xsl:output.
     *
     * <p>If argument to this function is null, any properties
     * previously set are removed, and the value will revert to the value
     * defined in the templates object.</p>
     *
     * <p>Pass a qualified property key name as a two-part string, the namespace URI
     * enclosed in curly braces ({}), followed by the local name. If the
     * name has a null URL, the String only contain the local name. An
     * application can safely check for a non-null URI by testing to see if the first
     * character of the name is a '{' character.</p>
     * <p>For example, if a URI and local name were obtained from an element
     * defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
     * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
     * no prefix is used.</p>
     *
     * @param oformat A set of output properties that will be
     *                used to override any of the same properties in affect
     *                for the transformation.
     * @throws IllegalArgumentException if any of the argument keys are not
     *                                  recognized and are not namespace qualified.
     * @see javax.xml.transform.OutputKeys
     * @see java.util.Properties
     */
    public void setOutputProperties(Properties oformat)
        throws IllegalArgumentException {

        if (null != oformat) {

            // See if an *explicit* method was set.
            String method = (String) oformat.get(OutputKeys.METHOD);

            if (null != method)
                m_outputFormat = new OutputProperties(method);
            else
                m_outputFormat = new OutputProperties();

            m_outputFormat.copyFrom(oformat);
        } else {
            // if oformat is null JAXP says that any props previously set are removed
            // and we are to revert back to those in the templates object (i.e. Stylesheet).
            m_outputFormat = null;
        }
    }

    /**
     * Set an output property that will be in effect for the
     * transformation.
     *
     * <p>Pass a qualified property name as a two-part string, the namespace URI
     * enclosed in curly braces ({}), followed by the local name. If the
     * name has a null URL, the String only contain the local name. An
     * application can safely check for a non-null URI by testing to see if the first
     * character of the name is a '{' character.</p>
     * <p>For example, if a URI and local name were obtained from an element
     * defined with &lt;xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/&gt;,
     * then the qualified name would be "{http://xyz.foo.com/yada/baz.html}foo". Note that
     * no prefix is used.</p>
     *
     * <p>The Properties object that was passed to {@link #setOutputProperties} won't
     * be effected by calling this method.</p>
     *
     * @param name  A non-null String that specifies an output
     *              property name, which may be namespace qualified.
     * @param value The non-null string value of the output property.
     * @throws IllegalArgumentException If the property is not supported, and is
     *                                  not qualified with a namespace.
     * @see javax.xml.transform.OutputKeys
     */
    public void setOutputProperty(String name, String value)
        throws IllegalArgumentException {

        if (!OutputProperties.isLegalPropertyKey(name))
            throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
        //+ name);

        m_outputFormat.setProperty(name, value);
    }

    /**
     * Get an output property that is in effect for the
     * transformation.  The property specified may be a property
     * that was set with setOutputProperty, or it may be a
     * property specified in the stylesheet.
     *
     * @param name A non-null String that specifies an output
     *             property name, which may be namespace qualified.
     * @return The string value of the output property, or null
     * if no property was found.
     * @throws IllegalArgumentException If the property is not supported.
     * @see javax.xml.transform.OutputKeys
     */
    public String getOutputProperty(String name) throws IllegalArgumentException {

        String           value = null;
        OutputProperties props = m_outputFormat;

        value = props.getProperty(name);

        if (null == value) {
            if (!OutputProperties.isLegalPropertyKey(name))
                throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{name})); //"output property not recognized: "
            // + name);
        }

        return value;
    }

    /**
     * Get the error event handler in effect for the transformation.
     *
     * @return The current error handler, which should never be null.
     */
    public ErrorListener getErrorListener() {
        return m_errorListener;
    }

    /**
     * Set the error event listener in effect for the transformation.
     *
     * @param listener The new error listener.
     * @throws IllegalArgumentException if listener is null.
     */
    public void setErrorListener(ErrorListener listener)
        throws IllegalArgumentException {
        if (listener == null)
            throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_NULL_ERROR_HANDLER, null));
        else
            m_errorListener = listener;
    }

    /**
     * Receive notification of a notation declaration.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass if they wish to keep track of the notations
     * declared in a document.</p>
     *
     * @param name     The notation name.
     * @param publicId The notation public identifier, or null if not
     *                 available.
     * @param systemId The notation system identifier.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.DTDHandler#notationDecl
     */
    public void notationDecl(String name, String publicId, String systemId)
        throws SAXException {
        if (null != m_resultDTDHandler)
            m_resultDTDHandler.notationDecl(name, publicId, systemId);
    }

    /**
     * Receive notification of an unparsed entity declaration.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to keep track of the unparsed entities
     * declared in a document.</p>
     *
     * @param name         The entity name.
     * @param publicId     The entity public identifier, or null if not
     *                     available.
     * @param systemId     The entity system identifier.
     * @param notationName The name of the associated notation.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
     */
    public void unparsedEntityDecl(
        String name, String publicId, String systemId, String notationName)
        throws SAXException {

        if (null != m_resultDTDHandler)
            m_resultDTDHandler.unparsedEntityDecl(name, publicId, systemId,
                notationName);
    }

    /**
     * Receive a Locator object for document events.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass if they wish to store the locator for use
     * with other document events.</p>
     *
     * @param locator A locator for all SAX document events.
     * @see org.xml.sax.ContentHandler#setDocumentLocator
     * @see org.xml.sax.Locator
     */
    public void setDocumentLocator(Locator locator) {
        try {
            if (null == m_resultContentHandler)
                createResultContentHandler(m_result);
        } catch (TransformerException te) {
            throw new wx.xml.xalan.xml.utils.WrappedRuntimeException(te);
        }

        m_resultContentHandler.setDocumentLocator(locator);
    }

    /**
     * Receive notification of the beginning of the document.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the beginning
     * of a document (such as allocating the root node of a tree or
     * creating an output file).</p>
     *
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#startDocument
     */
    public void startDocument() throws SAXException {

        try {
            if (null == m_resultContentHandler)
                createResultContentHandler(m_result);
        } catch (TransformerException te) {
            throw new SAXException(te.getMessage(), te);
        }

        // Reset for multiple transforms with this transformer.
        m_flushedStartDoc = false;
        m_foundFirstElement = false;
    }

    protected final void flushStartDoc()
        throws SAXException {
        if (!m_flushedStartDoc) {
            if (m_resultContentHandler == null) {
                try {
                    createResultContentHandler(m_result);
                } catch (TransformerException te) {
                    throw new SAXException(te);
                }
            }
            m_resultContentHandler.startDocument();
            m_flushedStartDoc = true;
        }
    }

    /**
     * Receive notification of the end of the document.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the end
     * of a document (such as finalising a tree or closing an output
     * file).</p>
     *
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#endDocument
     */
    public void endDocument() throws SAXException {
        flushStartDoc();
        m_resultContentHandler.endDocument();
    }

    /**
     * Receive notification of the start of a Namespace mapping.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the start of
     * each Namespace prefix scope (such as storing the prefix mapping).</p>
     *
     * @param prefix The Namespace prefix being declared.
     * @param uri    The Namespace URI mapped to the prefix.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#startPrefixMapping
     */
    public void startPrefixMapping(String prefix, String uri)
        throws SAXException {
        flushStartDoc();
        m_resultContentHandler.startPrefixMapping(prefix, uri);
    }

    // Implement DeclHandler

    /**
     * Receive notification of the end of a Namespace mapping.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the end of
     * each prefix mapping.</p>
     *
     * @param prefix The Namespace prefix being declared.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#endPrefixMapping
     */
    public void endPrefixMapping(String prefix) throws SAXException {
        flushStartDoc();
        m_resultContentHandler.endPrefixMapping(prefix);
    }

    /**
     * Receive notification of the start of an element.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the start of
     * each element (such as allocating a new tree node or writing
     * output to a file).</p>
     *
     * @param uri        The Namespace URI, or the empty string if the
     *                   element has no Namespace URI or if Namespace
     *                   processing is not being performed.
     * @param localName  The local name (without prefix), or the
     *                   empty string if Namespace processing is not being
     *                   performed.
     * @param qName      The qualified name (with prefix), or the
     *                   empty string if qualified names are not available.
     * @param attributes The specified or defaulted attributes.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#startElement
     */
    public void startElement(
        String uri, String localName, String qName, Attributes attributes)
        throws SAXException {

        if (!m_foundFirstElement && null != m_serializer) {
            m_foundFirstElement = true;

            Serializer newSerializer;

            try {
                newSerializer = SerializerSwitcher.switchSerializerIfHTML(uri,
                    localName, m_outputFormat.getProperties(), m_serializer);
            } catch (TransformerException te) {
                throw new SAXException(te);
            }

            if (newSerializer != m_serializer) {
                try {
                    m_resultContentHandler = newSerializer.asContentHandler();
                } catch (IOException ioe)  // why?
                {
                    throw new SAXException(ioe);
                }

                if (m_resultContentHandler instanceof DTDHandler)
                    m_resultDTDHandler = (DTDHandler) m_resultContentHandler;

                if (m_resultContentHandler instanceof LexicalHandler)
                    m_resultLexicalHandler = (LexicalHandler) m_resultContentHandler;

                m_serializer = newSerializer;
            }
        }
        flushStartDoc();
        m_resultContentHandler.startElement(uri, localName, qName, attributes);
    }

    /**
     * Receive notification of the end of an element.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the end of
     * each element (such as finalising a tree node or writing
     * output to a file).</p>
     *
     * @param uri       The Namespace URI, or the empty string if the
     *                  element has no Namespace URI or if Namespace
     *                  processing is not being performed.
     * @param localName The local name (without prefix), or the
     *                  empty string if Namespace processing is not being
     *                  performed.
     * @param qName     The qualified name (with prefix), or the
     *                  empty string if qualified names are not available.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#endElement
     */
    public void endElement(String uri, String localName, String qName)
        throws SAXException {
        m_resultContentHandler.endElement(uri, localName, qName);
    }

    /**
     * Receive notification of character data inside an element.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method to take specific actions for each chunk of character data
     * (such as adding the data to a node or buffer, or printing it to
     * a file).</p>
     *
     * @param ch     The characters.
     * @param start  The start position in the character array.
     * @param length The number of characters to use from the
     *               character array.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#characters
     */
    public void characters(char ch[], int start, int length) throws SAXException {
        flushStartDoc();
        m_resultContentHandler.characters(ch, start, length);
    }

    /**
     * Receive notification of ignorable whitespace in element content.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method to take specific actions for each chunk of ignorable
     * whitespace (such as adding data to a node or buffer, or printing
     * it to a file).</p>
     *
     * @param ch     The whitespace characters.
     * @param start  The start position in the character array.
     * @param length The number of characters to use from the
     *               character array.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#ignorableWhitespace
     */
    public void ignorableWhitespace(char ch[], int start, int length)
        throws SAXException {
        m_resultContentHandler.ignorableWhitespace(ch, start, length);
    }

    /**
     * Receive notification of a processing instruction.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions for each
     * processing instruction, such as setting status variables or
     * invoking other methods.</p>
     *
     * @param target The processing instruction target.
     * @param data   The processing instruction data, or null if
     *               none is supplied.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#processingInstruction
     */
    public void processingInstruction(String target, String data)
        throws SAXException {
        flushStartDoc();
        m_resultContentHandler.processingInstruction(target, data);
    }

    /**
     * Receive notification of a skipped entity.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions for each
     * processing instruction, such as setting status variables or
     * invoking other methods.</p>
     *
     * @param name The name of the skipped entity.
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *                                  wrapping another exception.
     * @throws SAXException
     * @see org.xml.sax.ContentHandler#processingInstruction
     */
    public void skippedEntity(String name) throws SAXException {
        flushStartDoc();
        m_resultContentHandler.skippedEntity(name);
    }

    /**
     * Report the start of DTD declarations, if any.
     *
     * <p>Any declarations are assumed to be in the internal subset
     * unless otherwise indicated by a {@link #startEntity startEntity}
     * event.</p>
     *
     * <p>Note that the start/endDTD events will appear within
     * the start/endDocument events from ContentHandler and
     * before the first startElement event.</p>
     *
     * @param name     The document type name.
     * @param publicId The declared public identifier for the
     *                 external DTD subset, or null if none was declared.
     * @param systemId The declared system identifier for the
     *                 external DTD subset, or null if none was declared.
     * @throws SAXException The application may raise an
     *                      exception.
     * @see #endDTD
     * @see #startEntity
     */
    public void startDTD(String name, String publicId, String systemId)
        throws SAXException {
        flushStartDoc();
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.startDTD(name, publicId, systemId);
    }

    /**
     * Report the end of DTD declarations.
     *
     * @throws SAXException The application may raise an exception.
     * @see #startDTD
     */
    public void endDTD() throws SAXException {
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.endDTD();
    }

    /**
     * Report the beginning of an entity in content.
     *
     * <p><strong>NOTE:</entity> entity references in attribute
     * values -- and the start and end of the document entity --
     * are never reported.</p>
     *
     * <p>The start and end of the external DTD subset are reported
     * using the pseudo-name "[dtd]".  All other events must be
     * properly nested within start/end entity events.</p>
     *
     * <p>Note that skipped entities will be reported through the
     * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
     * event, which is part of the ContentHandler interface.</p>
     *
     * @param name The name of the entity.  If it is a parameter
     *             entity, the name will begin with '%'.
     * @throws SAXException The application may raise an exception.
     * @see #endEntity
     * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
     * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
     */
    public void startEntity(String name) throws SAXException {
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.startEntity(name);
    }

    /**
     * Report the end of an entity.
     *
     * @param name The name of the entity that is ending.
     * @throws SAXException The application may raise an exception.
     * @see #startEntity
     */
    public void endEntity(String name) throws SAXException {
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.endEntity(name);
    }

    /**
     * Report the start of a CDATA section.
     *
     * <p>The contents of the CDATA section will be reported through
     * the regular {@link org.xml.sax.ContentHandler#characters
     * characters} event.</p>
     *
     * @throws SAXException The application may raise an exception.
     * @see #endCDATA
     */
    public void startCDATA() throws SAXException {
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.startCDATA();
    }

    /**
     * Report the end of a CDATA section.
     *
     * @throws SAXException The application may raise an exception.
     * @see #startCDATA
     */
    public void endCDATA() throws SAXException {
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.endCDATA();
    }

    /**
     * Report an XML comment anywhere in the document.
     *
     * <p>This callback will be used for comments inside or outside the
     * document element, including comments in the external DTD
     * subset (if read).</p>
     *
     * @param ch     An array holding the characters in the comment.
     * @param start  The starting position in the array.
     * @param length The number of characters to use from the array.
     * @throws SAXException The application may raise an exception.
     */
    public void comment(char ch[], int start, int length) throws SAXException {
        flushStartDoc();
        if (null != m_resultLexicalHandler)
            m_resultLexicalHandler.comment(ch, start, length);
    }

    /**
     * Report an element type declaration.
     *
     * <p>The content model will consist of the string "EMPTY", the
     * string "ANY", or a parenthesised group, optionally followed
     * by an occurrence indicator.  The model will be normalized so
     * that all whitespace is removed,and will include the enclosing
     * parentheses.</p>
     *
     * @param name  The element type name.
     * @param model The content model as a normalized string.
     * @throws SAXException The application may raise an exception.
     */
    public void elementDecl(String name, String model)
        throws SAXException {
        if (null != m_resultDeclHandler)
            m_resultDeclHandler.elementDecl(name, model);
    }

    /**
     * Report an attribute type declaration.
     *
     * <p>Only the effective (first) declaration for an attribute will
     * be reported.  The type will be one of the strings "CDATA",
     * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
     * "ENTITIES", or "NOTATION", or a parenthesized token group with
     * the separator "|" and all whitespace removed.</p>
     *
     * @param eName        The name of the associated element.
     * @param aName        The name of the attribute.
     * @param type         A string representing the attribute type.
     * @param valueDefault A string representing the attribute default
     *                     ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
     *                     none of these applies.
     * @param value        A string representing the attribute's default value,
     *                     or null if there is none.
     * @throws SAXException The application may raise an exception.
     */
    public void attributeDecl(String eName,
                              String aName,
                              String type,
                              String valueDefault,
                              String value)
        throws SAXException {
        if (null != m_resultDeclHandler)
            m_resultDeclHandler.attributeDecl(eName, aName, type, valueDefault, value);
    }

    /**
     * Report an internal entity declaration.
     *
     * <p>Only the effective (first) declaration for each entity
     * will be reported.</p>
     *
     * @param name  The name of the entity.  If it is a parameter
     *              entity, the name will begin with '%'.
     * @param value The replacement text of the entity.
     * @throws SAXException The application may raise an exception.
     * @see #externalEntityDecl
     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
     */
    public void internalEntityDecl(String name, String value)
        throws SAXException {
        if (null != m_resultDeclHandler)
            m_resultDeclHandler.internalEntityDecl(name, value);
    }

    /**
     * Report a parsed external entity declaration.
     *
     * <p>Only the effective (first) declaration for each entity
     * will be reported.</p>
     *
     * @param name     The name of the entity.  If it is a parameter
     *                 entity, the name will begin with '%'.
     * @param publicId The declared public identifier of the entity, or
     *                 null if none was declared.
     * @param systemId The declared system identifier of the entity.
     * @throws SAXException The application may raise an exception.
     * @see #internalEntityDecl
     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
     */
    public void externalEntityDecl(String name, String publicId,
                                   String systemId)
        throws SAXException {
        if (null != m_resultDeclHandler)
            m_resultDeclHandler.externalEntityDecl(name, publicId, systemId);
    }
}
